// -*-C++-*-

#ifndef VEC_QPX_DOUBLE4_H
#define VEC_QPX_DOUBLE4_H

#include "floatprops.h"
#include "mathfuncs.h"
#include "vec_base.h"

#include <cmath>

// QPX intrinsics
#ifdef __clang__
#include <qpxintrin.h>
#else
#include <builtins.h>
#endif
#include <mass_simd.h>

namespace vecmathlib {

#define VECMATHLIB_HAVE_VEC_DOUBLE_4
template <> struct boolvec<double, 4>;
template <> struct intvec<double, 4>;
template <> struct realvec<double, 4>;

template <> struct boolvec<double, 4> : floatprops<double> {
  static int const size = 4;
  typedef bool scalar_t;
  typedef vector4double bvector_t;
  static int const alignment = sizeof(bvector_t);

  static_assert(size * sizeof(real_t) == sizeof(bvector_t),
                "vector size is wrong");

private:
  // canonical true is +1.0, canonical false is -1.0
  // >=0 is true, -0 is true, nan is false
  static real_t from_bool(bool a) { return a ? +1.0 : -1.0; }
  static bool to_bool(real_t a) { return a >= 0.0; }

public:
  typedef boolvec boolvec_t;
  typedef intvec<real_t, size> intvec_t;
  typedef realvec<real_t, size> realvec_t;

  // Short names for type casts
  typedef real_t R;
  typedef int_t I;
  typedef uint_t U;
  typedef realvec_t RV;
  typedef intvec_t IV;
  typedef boolvec_t BV;
  typedef floatprops<real_t> FP;
  typedef mathfuncs<realvec_t> MF;

  bvector_t v;

  boolvec() {}
  boolvec(bvector_t x) : v(x) {}
  boolvec(bool a) : v(vec_splats(from_bool(a))) {}
  boolvec(const bool *as) {
    for (int d = 0; d < size; ++d)
      set_elt(d, as[d]);
  }

  operator bvector_t() const { return v; }
  bool operator[](int n) const { return to_bool(v[n]); }
  boolvec &set_elt(int n, bool a) { return v[n] = from_bool(a), *this; }

  intvec_t as_int() const;      // defined after intvec
  intvec_t convert_int() const; // defined after intvec

  boolvec operator!() const { return vec_not(v); }

  boolvec operator&&(boolvec x) const { return vec_and(v, x.v); }
  boolvec operator||(boolvec x) const { return vec_or(v, x.v); }
  boolvec operator==(boolvec x) const { return vec_logical(v, x.v, 0x9); }
  boolvec operator!=(boolvec x) const { return vec_xor(v, x.v); }

  bool all() const {
    // return (*this)[0] && (*this)[1] && (*this)[2] && (*this)[3];
    boolvec x0123 = *this;
    boolvec x1032 = vec_perm(x0123, x0123, vec_gpci(01032));
    boolvec y0022 = x0123 && x1032;
    return y0022[0] && y0022[2];
  }
  bool any() const {
    // return (*this)[0] || (*this)[1] || (*this)[2] || (*this)[3];
    boolvec x0123 = *this;
    boolvec x1032 = vec_perm(x0123, x0123, vec_gpci(01032));
    boolvec y0022 = x0123 || x1032;
    return y0022[0] || y0022[2];
  }

  // ifthen(condition, then-value, else-value)
  boolvec_t ifthen(boolvec_t x, boolvec_t y) const;
  intvec_t ifthen(intvec_t x, intvec_t y) const;    // defined after intvec
  realvec_t ifthen(realvec_t x, realvec_t y) const; // defined after realvec
};

template <> struct intvec<double, 4> : floatprops<double> {
  static int const size = 4;
  typedef int_t scalar_t;
  typedef vector4double ivector_t;
  static int const alignment = sizeof(ivector_t);

  static_assert(size * sizeof(real_t) == sizeof(ivector_t),
                "vector size is wrong");

  typedef boolvec<real_t, size> boolvec_t;
  typedef intvec intvec_t;
  typedef realvec<real_t, size> realvec_t;

  // Short names for type casts
  typedef real_t R;
  typedef int_t I;
  typedef uint_t U;
  typedef realvec_t RV;
  typedef intvec_t IV;
  typedef boolvec_t BV;
  typedef floatprops<real_t> FP;
  typedef mathfuncs<realvec_t> MF;

  ivector_t v;

  intvec() {}
  // Can't have a non-trivial copy constructor; if so, objects won't
  // be passed in registers
  // intvec(const intvec& x): v(x.v) {}
  // intvec& operator=(const intvec& x) { return v=x.v, *this; }
  intvec(ivector_t x) : v(x) {}
  intvec(int_t a) : v(vec_splats(FP::as_float(a))) {}
  intvec(const int_t *as) {
    for (int d = 0; d < size; ++d)
      set_elt(d, as[d]);
  }
  static intvec iota() {
    const int_t iota_[] = {0, 1, 2, 3};
    return intvec(iota_);
  }

  operator ivector_t() const { return v; }
  int_t operator[](int n) const { return FP::as_int(v[n]); }
  intvec &set_elt(int n, int_t a) { return v[n] = FP::as_float(a), *this; }

  // Vector casts do not change the bit battern
  boolvec_t as_bool() const { return v; }
  boolvec_t convert_bool() const { return *this != IV(I(0)); }
  realvec_t as_float() const;      // defined after realvec
  realvec_t convert_float() const; // defined after realvec

  intvec operator+() const { return *this; }
  intvec operator-() const {
    intvec r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, -(*this)[d]);
    return r;
  }

  intvec operator+(intvec x) const {
    intvec r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, (*this)[d] + x[d]);
    return r;
  }
  intvec operator-(intvec x) const {
    intvec r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, (*this)[d] - x[d]);
    return r;
  }

  intvec &operator+=(intvec x) { return *this = *this + x; }
  intvec &operator-=(intvec x) { return *this = *this - x; }

  intvec operator~() const {
    intvec r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, ~(*this)[d]);
    return r;
  }

  intvec operator&(intvec x) const {
    intvec r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, (*this)[d] & x[d]);
    return r;
  }
  intvec operator|(intvec x) const {
    intvec r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, (*this)[d] | x[d]);
    return r;
  }
  intvec operator^(intvec x) const {
    intvec r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, (*this)[d] ^ x[d]);
    return r;
  }

  intvec &operator&=(intvec x) { return *this = *this & x; }
  intvec &operator|=(intvec x) { return *this = *this | x; }
  intvec &operator^=(intvec x) { return *this = *this ^ x; }

  intvec_t bitifthen(intvec_t x, intvec_t y) const;

  intvec_t lsr(int_t n) const {
    intvec_t r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, U((*this)[d]) >> U(n));
    return r;
  }
  intvec_t rotate(int_t n) const;
  intvec operator>>(int_t n) const {
    intvec r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, (*this)[d] >> n);
    return r;
  }
  intvec operator<<(int_t n) const {
    intvec r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, (*this)[d] << n);
    return r;
  }
  intvec &operator>>=(int_t n) { return *this = *this >> n; }
  intvec &operator<<=(int_t n) { return *this = *this << n; }

  intvec_t lsr(intvec_t n) const {
    intvec_t r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, U((*this)[d]) >> U(n[d]));
    return r;
  }
  intvec_t rotate(intvec_t n) const;
  intvec operator>>(intvec n) const {
    intvec r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, (*this)[d] >> n[d]);
    return r;
  }
  intvec operator<<(intvec n) const {
    intvec r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, (*this)[d] << n[d]);
    return r;
  }
  intvec &operator>>=(intvec n) { return *this = *this >> n; }
  intvec &operator<<=(intvec n) { return *this = *this << n; }

  intvec_t clz() const;
  intvec_t popcount() const;

  boolvec_t operator==(intvec x) const {
    boolvec_t r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, (*this)[d] == x[d]);
    return r;
  }
  boolvec_t operator!=(intvec x) const {
    boolvec_t r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, (*this)[d] != x[d]);
    return r;
  }
  boolvec_t operator<(intvec x) const {
    boolvec_t r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, (*this)[d] < x[d]);
    return r;
  }
  boolvec_t operator<=(intvec x) const {
    boolvec_t r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, (*this)[d] <= x[d]);
    return r;
  }
  boolvec_t operator>(intvec x) const {
    boolvec_t r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, (*this)[d] > x[d]);
    return r;
  }
  boolvec_t operator>=(intvec x) const {
    boolvec_t r;
    for (int d = 0; d < size; ++d)
      r.set_elt(d, (*this)[d] >= x[d]);
    return r;
  }

  intvec_t abs() const;
  boolvec_t isignbit() const;
  intvec_t max(intvec_t x) const;
  intvec_t min(intvec_t x) const;
};

template <> struct realvec<double, 4> : floatprops<double> {
  static int const size = 4;
  typedef real_t scalar_t;
  typedef vector4double vector_t;
  static int const alignment = sizeof(vector_t);

  static const char *name() { return "<QPX:4*double>"; }
  void barrier() { __asm__("" : "+v"(v)); }

  static_assert(size * sizeof(real_t) == sizeof(vector_t),
                "vector size is wrong");

  typedef boolvec<real_t, size> boolvec_t;
  typedef intvec<real_t, size> intvec_t;
  typedef realvec realvec_t;

  // Short names for type casts
  typedef real_t R;
  typedef int_t I;
  typedef uint_t U;
  typedef realvec_t RV;
  typedef intvec_t IV;
  typedef boolvec_t BV;
  typedef floatprops<real_t> FP;
  typedef mathfuncs<realvec_t> MF;

  vector_t v;

  realvec() {}
  // Can't have a non-trivial copy constructor; if so, objects won't
  // be passed in registers
  // realvec(const realvec& x): v(x.v) {}
  // realvec& operator=(const realvec& x) { return v=x.v, *this; }
  realvec(vector_t x) : v(x) {}
  realvec(real_t a) : v(vec_splats(a)) {}
  realvec(const real_t *as) {
    for (int d = 0; d < size; ++d)
      set_elt(d, as[d]);
  }

  operator vector_t() const { return v; }
  real_t operator[](int n) const { return v[n]; }
  realvec &set_elt(int n, real_t a) { return v[n] = a, *this; }

  typedef vecmathlib::mask_t<realvec_t> mask_t;

  static realvec_t loada(const real_t *p) {
    VML_ASSERT(intptr_t(p) % alignment == 0);
    return vec_lda(0, (real_t *)p);
  }
  static realvec_t loadu(const real_t *p) {
    realvec_t v0 = vec_ld(0, (real_t *)p);
    realvec_t v1 = vec_ld(31, (real_t *)p);
    return vec_perm(v0.v, v1.v, vec_lvsl(0, (real_t *)p));
  }
  static realvec_t loadu(const real_t *p, std::ptrdiff_t ioff) {
    VML_ASSERT(intptr_t(p) % alignment == 0);
    if (ioff % realvec::size == 0)
      return loada(p + ioff);
    // TODO: use load instruction with fixed offset
    return loadu(p + ioff);
  }
  realvec_t loada(const real_t *p, mask_t m) const {
    VML_ASSERT(intptr_t(p) % alignment == 0);
    if (__builtin_expect(all(m.m), true)) {
      return loada(p);
    } else {
      return m.m.ifthen(loada(p), *this);
    }
  }
  realvec_t loadu(const real_t *p, mask_t m) const {
    if (__builtin_expect(m.all_m, true)) {
      return loadu(p);
    } else {
      return m.m.ifthen(loadu(p), *this);
    }
  }
  realvec_t loadu(const real_t *p, std::ptrdiff_t ioff, mask_t m) const {
    VML_ASSERT(intptr_t(p) % alignment == 0);
    if (ioff % realvec::size == 0)
      return loada(p + ioff, m);
    // TODO: use load instruction with fixed offset
    return loadu(p + ioff, m);
  }

  void storea(real_t *p) const {
    VML_ASSERT(intptr_t(p) % alignment == 0);
    vec_sta(v, 0, p);
  }
  void storeu(real_t *p) const {
    // Vector stores would require vector loads, which would need to
    // be atomic
    // TODO: see <https://developer.apple.com/hardwaredrivers/ve/alignment.html>
    // for good ideas
    p[0] = (*this)[0];
    p[1] = (*this)[1];
    p[2] = (*this)[2];
    p[3] = (*this)[3];
  }
  void storeu(real_t *p, std::ptrdiff_t ioff) const {
    VML_ASSERT(intptr_t(p) % alignment == 0);
    if (ioff % realvec::size == 0)
      return storea(p + ioff);
    storeu(p + ioff);
  }
  void storea(real_t *p, mask_t m) const {
    VML_ASSERT(intptr_t(p) % alignment == 0);
    if (__builtin_expect(m.all_m, true)) {
      storea(p);
    } else {
      if (m.m[0])
        p[0] = (*this)[0];
      if (m.m[1])
        p[1] = (*this)[1];
      if (m.m[2])
        p[2] = (*this)[2];
      if (m.m[3])
        p[3] = (*this)[3];
    }
  }
  void storeu(real_t *p, mask_t m) const {
    if (__builtin_expect(m.all_m, true)) {
      storeu(p);
    } else {
      if (m.m[0])
        p[0] = (*this)[0];
      if (m.m[1])
        p[1] = (*this)[1];
      if (m.m[2])
        p[2] = (*this)[2];
      if (m.m[3])
        p[3] = (*this)[3];
    }
  }
  void storeu(real_t *p, std::ptrdiff_t ioff, mask_t m) const {
    VML_ASSERT(intptr_t(p) % alignment == 0);
    if (ioff % realvec::size == 0)
      return storea(p + ioff, m);
    storeu(p + ioff, m);
  }

  intvec_t as_int() const { return v; }
  intvec_t convert_int() const { return vec_ctidz(v); }

  realvec operator+() const { return *this; }
  realvec operator-() const { return vec_neg(v); }

  realvec operator+(realvec x) const { return vec_add(v, x.v); }
  realvec operator-(realvec x) const { return vec_sub(v, x.v); }
  realvec operator*(realvec x) const { return vec_mul(v, x.v); }
  realvec operator/(realvec x) const {
    // return vec_swdiv_nochk(v, x.v);
    return div_fastd4(v, x.v);
  }

  realvec &operator+=(realvec x) { return *this = *this + x; }
  realvec &operator-=(realvec x) { return *this = *this - x; }
  realvec &operator*=(realvec x) { return *this = *this * x; }
  realvec &operator/=(realvec x) { return *this = *this / x; }

  real_t maxval() const {
    // return vml_std::fmax(vml_std::fmax((*this)[0], (*this)[1]),
    //                      vml_std::fmax((*this)[2], (*this)[3]));
    realvec_t x0123 = *this;
    realvec_t x1032 = vec_perm(x0123, x0123, vec_gpci(01032));
    realvec_t y0022 = x0123.fmax(x1032);
    return vml_std::fmax(y0022[0], y0022[2]);
  }
  real_t minval() const {
    // return vml_std::fmin(vml_std::fmin((*this)[0], (*this)[1]),
    //                      vml_std::fmin((*this)[2], (*this)[3]));
    realvec_t x0123 = *this;
    realvec_t x1032 = vec_perm(x0123, x0123, vec_gpci(01032));
    realvec_t y0022 = x0123.fmin(x1032);
    return vml_std::fmin(y0022[0], y0022[2]);
  }
  real_t prod() const {
    // return (*this)[0] * (*this)[1] * (*this)[2] * (*this)[3];
    realvec_t x = vec_xmul(v, v);
    return x[1] * x[3];
  }
  real_t sum() const {
    // return (*this)[0] + (*this)[1] + (*this)[2] + (*this)[3];
    realvec_t c1 = vec_logical(v, v, 0xf); // +1.0
    realvec_t x = vec_xxmadd(v, c1, v);
    return x[0] + x[2];
  }

  boolvec_t operator==(realvec x) const { return vec_cmpeq(v, x.v); }
  boolvec_t operator!=(realvec x) const { return !(*this == x); }
  boolvec_t operator<(realvec x) const { return vec_cmplt(v, x.v); }
  boolvec_t operator<=(realvec x) const {
#ifdef VML_HAVE_NAN
    return *this < x || *this == x;
#else
    return !(*this > x);
#endif
  }
  boolvec_t operator>(realvec x) const { return vec_cmpgt(v, x.v); }
  boolvec_t operator>=(realvec x) const {
#ifdef VML_HAVE_NAN
    return *this > x || *this == x;
#else
    return !(*this < x);
#endif
  }

  realvec acos() const { return acosd4(v); }
  realvec acosh() const { return acoshd4(v); }
  realvec asin() const { return asind4(v); }
  realvec asinh() const { return asinhd4(v); }
  realvec atan() const { return atand4(v); }
  realvec atan2(realvec y) const { return atan2d4(v, y.v); }
  realvec atanh() const { return atanhd4(v); }
  realvec cbrt() const { return cbrtd4(v); }
  realvec ceil() const { return vec_ceil(v); }
  realvec copysign(realvec y) const { return vec_cpsgn(y.v, v); }
  realvec cos() const { return cosd4(v); }
  realvec cosh() const { return coshd4(v); }
  realvec exp() const { return expd4(v); }
  realvec exp10() const { return exp10d4(v); }
  realvec exp2() const { return exp2d4(v); }
  realvec expm1() const { return expm1d4(v); }
  realvec fabs() const { return vec_abs(v); }
  realvec fdim(realvec y) const { return MF::vml_fdim(*this, y); }
  realvec floor() const { return vec_floor(v); }
  realvec fma(realvec y, realvec z) const { return vec_madd(v, y.v, z.v); }
  realvec fmax(realvec y) const { return MF::vml_fmax(v, y.v); }
  realvec fmin(realvec y) const { return MF::vml_fmin(v, y.v); }
  realvec fmod(realvec y) const { return MF::vml_fmod(*this, y); }
  realvec frexp(intvec_t *r) const { return MF::vml_frexp(*this, r); }
  realvec hypot(realvec y) const { return hypotd4(v, y.v); }
  intvec_t ilogb() const {
    // int_t ilogb_[] = {
    //   ::ilogb((*this)[0]),
    //   ::ilogb((*this)[1]),
    //   ::ilogb((*this)[2]),
    //   ::ilogb((*this)[3])
    // };
    // return intvec_t(ilogb_);
    return MF::vml_ilogb(v);
  }
  boolvec_t isfinite() const { return MF::vml_isfinite(*this); }
  boolvec_t isinf() const { return MF::vml_isinf(*this); }
  boolvec_t isnan() const {
#ifdef VML_HAVE_NAN
    return vec_tstnan(v, v);
#else
    return BV(false);
#endif
  }
  boolvec_t isnormal() const { return MF::vml_isnormal(*this); }
  realvec ldexp(int_t n) const { return ldexp(intvec_t(n)); }
  realvec ldexp(intvec_t n) const {
    real_t ldexp_[] = {
        vml_std::ldexp((*this)[0], n[0]), vml_std::ldexp((*this)[1], n[1]),
        vml_std::ldexp((*this)[2], n[2]), vml_std::ldexp((*this)[3], n[3])};
    return realvec_t(ldexp_);
  }
  realvec log() const { return logd4(v); }
  realvec log10() const { return log10d4(v); }
  realvec log1p() const { return log1pd4(v); }
  realvec log2() const { return log2d4(v); }
  realvec_t mad(realvec_t y, realvec_t z) const {
    return MF::vml_mad(*this, y, z);
  }
  realvec nextafter(realvec y) const { return MF::vml_nextafter(*this, y); }
  realvec pow(realvec y) const { return powd4(v, y.v); }
  realvec rcp() const { return recip_fastd4(v); }
  realvec remainder(realvec y) const { return MF::vml_remainder(*this, y); }
  realvec rint() const {
    return MF::vml_rint(*this);
    // This is tempting, but seems too invasive
    // #ifdef VML_HAVE_FP_CONTRACT
    //       return MF::vml_rint(*this);
    // #else
    //       return vec_round(v);      // use round instead of rint
    // #endif
  }
  realvec round() const { return vec_round(v); }
  realvec rsqrt() const {
    realvec x = *this;
    realvec r = vec_rsqrte(x.v); // this is only an approximation
    // TODO: use fma
    // two Newton iterations (see vml_rsqrt)
    r += RV(0.5) * r * (RV(1.0) - x * r * r);
    r += RV(0.5) * r * (RV(1.0) - x * r * r);
    return r;
  }
  boolvec_t signbit() const {
    return !RV(1.0).copysign(*this).as_int().as_bool();
  }
  realvec sin() const { return sind4(v); }
  realvec sinh() const { return sinhd4(v); }
  realvec sqrt() const {
    // return vec_sqrtsw_nochk(v);
    return *this * rsqrt();
  }
  realvec tan() const { return tand4(v); }
  realvec tanh() const { return tanhd4(v); }
  realvec trunc() const { return vec_trunc(v); }
};

// boolvec definitions

inline intvec<double, 4> boolvec<double, 4>::as_int() const { return v; }

inline intvec<double, 4> boolvec<double, 4>::convert_int() const {
  return ifthen(IV(I(1)), IV(I(0)));
}

inline boolvec<double, 4> boolvec<double, 4>::ifthen(boolvec_t x,
                                                     boolvec_t y) const {
  return ifthen(x.as_int(), y.as_int()).as_bool();
}

inline intvec<double, 4> boolvec<double, 4>::ifthen(intvec_t x,
                                                    intvec_t y) const {
  return ifthen(x.as_float(), y.as_float()).as_int();
}

inline realvec<double, 4> boolvec<double, 4>::ifthen(realvec_t x,
                                                     realvec_t y) const {
  return vec_sel(y.v, x.v, v);
}

// intvec definitions

inline intvec<double, 4> intvec<double, 4>::abs() const {
  return MF::vml_abs(*this);
}

inline realvec<double, 4> intvec<double, 4>::as_float() const { return v; }

inline intvec<double, 4> intvec<double, 4>::bitifthen(intvec_t x,
                                                      intvec_t y) const {
  return MF::vml_bitifthen(*this, x, y);
}

inline intvec<double, 4> intvec<double, 4>::clz() const {
  return MF::vml_clz(*this);
}

inline realvec<double, 4> intvec<double, 4>::convert_float() const {
  return vec_cfid(v);
}

inline boolvec<double, 4> intvec<double, 4>::isignbit() const {
  return MF::vml_isignbit(*this);
}

inline intvec<double, 4> intvec<double, 4>::max(intvec_t x) const {
  return MF::vml_max(*this, x);
}

inline intvec<double, 4> intvec<double, 4>::min(intvec_t x) const {
  return MF::vml_min(*this, x);
}

inline intvec<double, 4> intvec<double, 4>::popcount() const {
  return MF::vml_popcount(*this);
}

inline intvec<double, 4> intvec<double, 4>::rotate(int_t n) const {
  return MF::vml_rotate(*this, n);
}

inline intvec<double, 4> intvec<double, 4>::rotate(intvec_t n) const {
  return MF::vml_rotate(*this, n);
}

} // namespace vecmathlib

#endif // #ifndef VEC_QPX_DOUBLE4_H
